/*
 * Copyright 2010 Katsunori Koyanagi
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

package sweezy.anim.impl
{
	import flash.events.Event;
	import flash.utils.Dictionary;
	import flash.utils.getTimer;
	import sweezy.anim.core.AnimationEvent;
	import sweezy.anim.core.IAnimation;

	internal class AnimationBase implements IAnimation
	{

		internal static var activeAnimations:Dictionary = new Dictionary();

		protected static function normalizeDuration(d:Number):uint
		{
			return d < 0 ? 0 : int.MAX_VALUE < d ? int.MAX_VALUE : uint(d);
		}

		protected var _events:Events;

		protected var _managedName:String;

		protected var _manager:AnimationManager;

		internal var _delta:int;

		internal var _duration:uint;

		internal var _next:AnimationBase;

		internal var _position:int;

		internal var _previous:AnimationBase;

		/**
		 * 0000|0000|0000|0000|0000|0000|0000|0000
		 *    ^   ^^           ^^^^ ^^^^      ^^^^- playing flag
		 *    |   ||           |||| ||||      ||+ stop flag
		 *    |   ||           |||| ||||      |+ managed flag
		 *    |   ||           |||| ||||      + init flag
		 *    |   ||           |||| |||| ---- UNUSED
		 *    |   ||           |||| |||+ update event flag(EventDispatcher)
		 *    |   ||           |||| ||+ start event flag(EventDispatcher)
		 *    |   ||           |||| |+ pause event flag(EventDispatcher)
		 *    |   ||           |||| + complete event flag(EventDispatcher)
		 *    |   ||           |||+ update event flag(AS2 Style)
		 *    |   ||           ||+ start event flag(AS2 Style)
		 *    |   ||           |+ pause event flag(AS2 Style)
		 *    |   ||           + complete event flag(AS2 Style)
		 *    |   ||      ---- exponent parameter of inline easing
		 *    |   || ---- easing type(0:none 1:IEasing 2:easeIn 3:easeOut 4:easeInOut)
		 *    |   |+ error event flag(EventDispatcher)
		 *    |   + error event flag(AS2 Style)
		 *    + last fraction is not zero
		 * ---- UNUSED
		 */
		internal var _state:uint;

		private var _data:Object;

		public function AnimationBase(manager:AnimationManager)
		{
			_manager = manager;
			_position = -1;
		}

		public function addEventListener(type:String, listener:Function, useCapture:Boolean = false, priority:int = 0, useWeakReference:Boolean = false):void
		{
			events.addEventListener(type, listener, useCapture, priority, useWeakReference);

			switch (type)
			{
				case "animationUpdate":
					_state |= 0x0100;
					break;
				case "animationStart":
					_state |= 0x0200;
					break;
				case "animationPause":
					_state |= 0x0400;
					break;
				case "animationComplete":
					_state |= 0x0800;
					break;
				case "animationError":
					_state |= 0x01000000;
					break;
			}
		}

		public function get autoRewind():Boolean
		{
			return (_state & 0x10) === 0x10;
		}

		public function set autoRewind(value:Boolean):void
		{
			if (value)
			{
				_state |= 0x10;
			}
			else
			{
				_state &= ~0x01;
			}
		}

		public function get data():Object
		{
			return _data;
		}

		public function set data(value:Object):void
		{
			_data = value;
		}

		public function dispatchEvent(event:Event):Boolean
		{
			return events.dispatchEvent(event);
		}

		public function get duration():uint
		{
			if ((_state & 0x08) === 0x00)
			{
				_state |= 0x08;
				onInitializeInternal();
			}

			return _duration;
		}

		public function gotoAndStart(position:int):IAnimation
		{
			this.position = position;
			start();

			return this;
		}

		public function gotoAndStop(position:int):IAnimation
		{
			this.position = position;
			pause();

			return this;
		}

		public function hasEventListener(type:String):Boolean
		{
			return events.hasEventListener(type);
		}

		public function initialize():void
		{
			if ((_state & 0x08) === 0x00)
			{
				_state |= 0x08;
				onInitializeInternal();
			}
		}

		public function get initialized():Boolean
		{
			return (_state & 0x08) === 0x08;
		}

		public function get onComplete():Function
		{
			return _events === null ? null : _events.onComplete;
		}

		public function set onComplete(value:Function):void
		{
			if (_events === null && value === null)
			{
				return;
			}

			events.onComplete = value;
			if (value !== null)
			{
				_state |= 0x8000;
			}
			else
			{
				_state &= ~0x8000;
			}
		}

		public function get onCompleteParams():Array
		{
			return _events === null ? null : _events.onCompleteParams;
		}

		public function set onCompleteParams(value:Array):void
		{
			if (_events === null && value === null)
			{
				return;
			}

			events.onCompleteParams = value;
		}

		public function get onCompleteScope():Object
		{
			return _events === null ? null : _events.onCompleteScope;
		}

		public function set onCompleteScope(value:Object):void
		{
			if (_events === null && value === null)
			{
				return;
			}

			events.onCompleteScope = value === null ? this : value;
		}

		public function get onPause():Function
		{
			return _events === null ? null : _events.onPause;
		}

		public function set onPause(value:Function):void
		{
			if (_events === null && value === null)
			{
				return;
			}

			events.onPause = value;
			if (value !== null)
			{
				_state |= 0x4000;
			}
			else
			{
				_state &= ~0x4000;
			}
		}

		public function get onPauseParams():Array
		{
			return _events === null ? null : _events.onPauseParams;
		}

		public function set onPauseParams(value:Array):void
		{
			if (_events === null && value === null)
			{
				return;
			}

			events.onPauseParams = value;
		}

		public function get onPauseScope():Object
		{
			return _events === null ? null : _events.onPauseScope;
		}

		public function set onPauseScope(value:Object):void
		{
			if (_events === null && value === null)
			{
				return;
			}

			events.onPauseScope = value === null ? this : value;
		}

		public function get onStart():Function
		{
			return _events === null ? null : _events.onStart;
		}

		public function set onStart(value:Function):void
		{
			if (_events === null && value === null)
			{
				return;
			}

			events.onStart = value;
			if (value !== null)
			{
				_state |= 0x2000;
			}
			else
			{
				_state &= ~0x2000;
			}
		}

		public function get onStartParams():Array
		{
			return _events === null ? null : _events.onStartParams;
		}

		public function set onStartParams(value:Array):void
		{
			if (_events === null && value === null)
			{
				return;
			}

			events.onStartParams = value;
		}

		public function get onStartScope():Object
		{
			return _events === null ? null : _events.onStartScope;
		}

		public function set onStartScope(value:Object):void
		{
			if (_events === null && value === null)
			{
				return;
			}

			events.onStartScope = value === null ? this : value;
		}

		public function get onUpdate():Function
		{
			return _events === null ? null : _events.onUpdate;
		}

		public function set onUpdate(value:Function):void
		{
			if (_events === null && value === null)
			{
				return;
			}

			events.onUpdate = value;
			if (value !== null)
			{
				_state |= 0x1000;
			}
			else
			{
				_state &= ~0x1000;
			}
		}

		public function get onUpdateParams():Array
		{
			return _events === null ? null : _events.onUpdateParams;
		}

		public function set onUpdateParams(value:Array):void
		{
			if (_events === null && value === null)
			{
				return;
			}

			events.onUpdateParams = value;
		}

		public function get onUpdateScope():Object
		{
			return _events === null ? null : _events.onUpdateScope;
		}

		public function set onUpdateScope(value:Object):void
		{
			if (_events === null && value === null)
			{
				return;
			}

			events.onUpdateScope = value === null ? this : value;
		}

		public function get parameters():Object
		{
			return {};
		}

		public function pause():IAnimation
		{
			if ((_state & 0x01) === 0x00)
			{
				return this;
			}

			if (_managedName !== null)
			{
				delete activeAnimations[_managedName];
				_managedName = null;
			}

			_state &= ~0x01;
			_state |= 0x02;

			if ((_state & 0x4400) !== 0x0000)
			{
				(_state & 0x0400) === 0x0400 && _events.dispatchEvent(new AnimationEvent("animationPause"));
				(_state & 0x4000) === 0x4000 && _events.onPause.apply(_events.onPauseScope, _events.onPauseParams);
			}

			return this;
		}

		public function get playing():Boolean
		{
			return (_state & 0x01) === 0x01;
		}

		public function get position():int
		{
			return _position;
		}

		public function set position(value:int):void
		{
			if ((_state & 0x08) === 0x00)
			{
				_state |= 0x08;
				onInitializeInternal();
			}

			var complete:int;
			if (value >= _duration)
			{
				if ((_state & 0x10) === 0x10)
				{
					_position = value - int(value / _duration) * _duration;
				}
				else
				{
					_position = _duration;
					complete = _state & 0x01;
				}
			}
			else if (value > -1)
			{
				_position = value;
			}
			else if (_position === -1)
			{
				return;
			}
			else
			{
				_position = -1;
			}

			onUpdateInternal();

			if ((_state & 0x1100) !== 0x0000)
			{
				(_state & 0x0100) === 0x0100 && _events.dispatchEvent(new AnimationEvent("animationUpdate"));
				(_state & 0x1000) === 0x1000 && _events.onUpdate.apply(_events.onUpdateScope, _events.onUpdateParams);
			}

			if (complete === 0x01)
			{
				if ((_state & 0x8800) !== 0x0000)
				{
					(_state & 0x0800) === 0x0800 && _events.dispatchEvent(new AnimationEvent("animationComplete"));
					(_state & 0x8000) === 0x8000 && _events.onComplete.apply(_events.onCompleteScope, _events.onCompleteParams);
				}
				pause();
			}
		}

		public function removeEventListener(type:String, listener:Function, useCapture:Boolean = false):void
		{
			events.removeEventListener(type, listener, useCapture);

			if (!events.hasEventListener(type))
			{
				switch (type)
				{
					case "animationUpdate":
						_state &= ~0x0100;
						break;
					case "animationStart":
						_state &= ~0x0200;
						break;
					case "animationPause":
						_state &= ~0x0400;
						break;
					case "animationComplete":
						_state &= ~0x0800;
						break;
					case "animationError":
						_state &= ~0x01000000;
				}
			}
		}

		public function restart():IAnimation
		{
			pause();
			position = -1;
			start();

			return this;
		}

		public function start(name:String = null):IAnimation
		{
			if ((_state & 0x01) === 0x01)
			{
				return this;
			}

			_managedName = name;
			if (name !== null)
			{
				if (name in activeAnimations)
				{
					activeAnimations[name].pause();
				}
				activeAnimations[name] = this;
			}

			if ((_state & 0x08) === 0x00)
			{
				_state |= 0x08;
				onInitializeInternal();
			}

			_state |= 0x01;
			_state &= ~0x02;
			_manager.startTimer(this);

			if ((_state & 0x2200) !== 0x0000)
			{
				(_state & 0x0200) === 0x0200 && _events.dispatchEvent(new AnimationEvent("animationStart"));
				(_state & 0x2000) === 0x2000 && _events.onStart.apply(_events.onStartScope, _events.onStartParams);
			}

			return this;
		}

		public function stop():IAnimation
		{
			pause();
			position = -1;

			return this;
		}

		public function update():IAnimation
		{
			if ((_state & 0x01) === 0x01)
			{
				position = _delta + getTimer();
			}
			else
			{
				position = _position;
			}

			return this;
		}

		public function updateFromTimer(value:int):AnimationBase
		{
			if ((_state & 0x02) !== 0x02)
			{
				var complete:int;
				value = _delta + value;
				if (value >= _duration)
				{
					if ((_state & 0x10) === 0x10)
					{
						_position = value - int(value / _duration) * _duration;
					}
					else
					{
						_position = _duration;
						complete = _state & 0x01;
					}
				}
				else if (value > -1)
				{
					_position = value;
				}
				else if (_position === -1)
				{
					return _next;
				}
				else
				{
					_position = -1;
				}

				onUpdateInternal();

				if ((_state & 0x1100) !== 0x0000)
				{
					(_state & 0x0100) === 0x0100 && _events.dispatchEvent(new AnimationEvent("animationUpdate"));
					(_state & 0x1000) === 0x1000 && _events.onUpdate.apply(_events.onUpdateScope, _events.onUpdateParams);
				}

				if (complete === 0x01)
				{
					if ((_state & 0x8800) !== 0x0000)
					{
						(_state & 0x0800) === 0x0800 && _events.dispatchEvent(new AnimationEvent("animationComplete"));
						(_state & 0x8000) === 0x8000 && _events.onComplete.apply(_events.onCompleteScope, _events.onCompleteParams);
					}
					pause();
				}
			}
			else
			{
				_next._previous = _previous;
				_previous._next = _next;
				--_manager._count;
				_state &= ~0x04;
			}

			return _next;
		}

		public function willTrigger(type:String):Boolean
		{
			return events.willTrigger(type);
		}

		protected function get animationName():String
		{
			return null;
		}

		protected function get events():Events
		{
			if (_events === null)
			{
				_events = new Events(this);
			}

			return _events;
		}

		protected function onInitializeInternal():void
		{
		}

		protected function onUpdateInternal():void
		{
		}
	}
}
